package top.jiangqiang.qyblog.common.exception;

import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.core.exceptions.ExceptionUtil;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import top.jiangqiang.qyblog.common.base.response.Result;
import top.jiangqiang.qyblog.common.constant.ResponseEnum;

import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * 全局异常处理器，将某些异常响应为json字符串
 *
 * @author jiangqiang
 */
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 方法参数校验失败时抛出
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(value = BindException.class)
    @ResponseBody
    public Result<?> bindExceptionHandler(BindException exception) {
        printLog(exception);
        List<ObjectError> allErrors = exception.getBindingResult().getAllErrors();
        //使用注解校验参数时，参数校验失败会进入此异常，这里只取其中一个异常提示返回
        String message = allErrors.stream().map(DefaultMessageSourceResolvable::getDefaultMessage).filter(Objects::nonNull).findFirst().orElse(exception.getMessage());
        return Result.error(message);
    }

    /**
     * satoken拦截器校验没有登录异常
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(value = NotLoginException.class)
    @ResponseBody
    public Result<?> bindNotLoginExceptionHandler(NotLoginException exception) {
        printLog(exception);
        return Result.build(ResponseEnum.UNAUTHORIZED);
    }

    /**
     * 上传文件时，文件为空
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    @ResponseBody
    public Result<?> validationExceptionHandler(ConstraintViolationException exception) {
        printLog(exception);
        Optional<ConstraintViolation<?>> constraintViolation = exception.getConstraintViolations().stream().findAny();
        String errorMessage = constraintViolation.map(ConstraintViolation::getMessage).orElseGet(exception::getMessage);
        return Result.error(errorMessage);
    }

    /**
     * 处理业务异常 JsonException
     * <p>
     * 例如说，业务逻辑错误
     */
    @ExceptionHandler(value = JsonException.class)
    @ResponseBody
    public Result<?> jsonExceptionHandler(JsonException exception) {
        printLog(exception);
        return Result.error(exception);
    }

    /**
     * 响应页面
     *
     * @param exception 需要响应页面的异常
     * @return
     */
    @ExceptionHandler(value = PageException.class)
    public String pageExceptionHandler(PageException exception, HttpServletRequest request) {
        printLog(exception);
        request.setAttribute("timestamp", new Date());
        request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, ResponseEnum.INTERNAL_SERVER_ERROR.getCode());
        request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
        return "forward:/error";
    }

    /**
     * 默认异常处理逻辑
     *
     * @param throwable
     * @return
     */
    @ExceptionHandler(value = Throwable.class)
    @ResponseBody
    public Result<?> defaultExceptionHandler(Throwable throwable) {
        printLog(throwable);
        return Result.error(throwable);
    }

    private static void printLog(Throwable throwable) {
        log.debug("[{}]:[{}]\n", throwable.getClass(), ExceptionUtil.getMessage(throwable));
        throwable.printStackTrace();
    }
}
